﻿using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;

namespace HoNSharp
{
    public delegate void SocketConnectedHandler(object sender, SocketConnectEventArgs e);
    public delegate void SocketConnectingHandler(object sender, SocketConnectEventArgs e);
    public delegate void SocketDisconnectedHandler(object sender, EventArgs e);
    public delegate void SocketDisconnectingHandler(object sender, EventArgs e);
    public delegate void SocketErroredHandler(object sender, SocketErrorEventArgs e);
    public delegate void SocketReceivedDataHandler(object sender, SocketRecvEventArgs e);
    public delegate void SocketSendDataHandler(object sender, SocketSendEventArgs e);

    public class SocketConnectEventArgs : EventArgs
    {
        private string _host;
        private UInt16 _port;

        public SocketConnectEventArgs(string RemoteHostIP, UInt16 RemotePort)
        {
            this._host = RemoteHostIP;
            this._port = RemotePort;
        }

        public string GetHost()
        {
            return this._host;
        }

        public UInt16 GetPort()
        {
            return this._port;
        }
    }
    public class SocketRecvEventArgs : EventArgs
    {
        private byte[] _buffer;
        private int _recvd;

        public SocketRecvEventArgs(int bytesReceived, byte[] data)
        {
            this._buffer = data;
            this._recvd = bytesReceived;
        }

        public int GetBytesReceived()
        {
            return this._recvd;
        }

        public byte[] GetData()
        {
            return this._buffer;
        }
    }
    public class SocketSendEventArgs : EventArgs
    {
        private int _sent;

        public SocketSendEventArgs(int bytesSent)
        {
            this._sent = bytesSent;
        }

        public int GetBytesSent()
        {
            return this._sent;
        }
    }
    public class SocketErrorEventArgs : EventArgs
    {
        private int _code;
        private string _text;

        public SocketErrorEventArgs(int ErrorCode, string ErrorText)
        {
            this._code = ErrorCode;
            this._text = ErrorText;
        }

        public int GetErrorCode()
        {
            return this._code;
        }

        public string GetErrorText()
        {
            return this._text;
        }
    }

    class AsynchronousTCPClient
    {
        class SocketPacket
        {
            public Socket assocSocket;
            public byte[] dataBuffer = new byte[4096];
        }

        public event SocketConnectedHandler onConnected;
        public event SocketConnectingHandler onConnecting;
        public event SocketDisconnectedHandler onDisconnected;
        public event SocketDisconnectingHandler onDisconnecting;
        public event SocketErroredHandler onError;
        public event SocketReceivedDataHandler onRecv;
        public event SocketSendDataHandler onSend;

        protected Socket _sck;
        protected string _host;
        protected UInt16 _port;

        public AsynchronousTCPClient()
        {
            
        }

        //
        // Public methods for controlling the socket
        //

        public void Connect(string RemoteHostIP, UInt16 RemotePort)
        {
            if (this._sck != null)
            {
                this._sck.Close();
                this._sck = null;
            }

            this._sck = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this._host = RemoteHostIP;
            this._port = RemotePort;
            try
            {
                this._sck.BeginConnect(new IPEndPoint(IPAddress.Parse(this._host), this._port), new AsyncCallback(this.onSocketConnected), this._sck);

                // Raise the event.
                this.OnConnectionAttempt(new SocketConnectEventArgs(this._host, this._port));
            }
            catch (SocketException se)
            {
                this.OnSocketError(new SocketErrorEventArgs(se.ErrorCode, se.Message));
            }
        }

        public void Disconnect()
        {
            try
            {
                this._sck.BeginDisconnect(true, new AsyncCallback(this.onSocketDisconnected), this._sck);
            }
            catch (SocketException se)
            {
                this.OnSocketError(new SocketErrorEventArgs(se.ErrorCode, se.Message));
            }
        }

        public void Send(byte[] data, int length)
        {
            try
            {
                this._sck.BeginSend(data, 0, length, SocketFlags.None, new AsyncCallback(onDataSent), this._sck);
            }
            catch (SocketException se)
            {
                this.OnSocketError(new SocketErrorEventArgs(se.ErrorCode, se.Message));
            }
        }

        public bool IsSocketConnected()
        {
            if (this._sck != null)
            {                
                return this._sck.Connected;
            }
            else
            {
                return false;
            }
        }

        //
        // Actual socket functions
        //

        private void onDataReceived(IAsyncResult ar)
        {
            SocketPacket myData = (SocketPacket)ar.AsyncState;

            try
            {
                int bRx = myData.assocSocket.EndReceive(ar);

                if (bRx > 0)
                {
                    // Raise the event.
                    this.OnRecieveData(new SocketRecvEventArgs(bRx, myData.dataBuffer));

                    // Resume the recv loop.
                    this.WaitForData();
                }
                else
                {
                    // Raise the event.
                    //this.OnDisconnect(new EventArgs());
                }
            }
            catch (SocketException se)
            {
                this.OnSocketError(new SocketErrorEventArgs(se.ErrorCode, se.Message));
            }
            catch (ObjectDisposedException)
            {
                // It's dead.  We're just still receiving?
            }
        }

        private void onDataSent(IAsyncResult ar)
        {
            Socket sck = (Socket)ar.AsyncState;

            try
            {
                int bTx = sck.EndSend(ar);

                // Raise the event.
                this.OnSendData(new SocketSendEventArgs(bTx));
            }
            catch (SocketException se)
            {
                this.OnSocketError(new SocketErrorEventArgs(se.ErrorCode, se.Message));
            }
        }

        private void onSocketConnected(IAsyncResult ar)
        {
            Socket sck = (Socket)ar.AsyncState;

            try
            {
                sck.EndConnect(ar);

                // Raise the event.
                this.OnConnect(new SocketConnectEventArgs(this._host, this._port));

                // Start the receiving loop.
                this.WaitForData();
            }
            catch (SocketException se)
            {
                this.OnSocketError(new SocketErrorEventArgs(se.ErrorCode, se.Message));
            }
        }

        private void onSocketDisconnected(IAsyncResult ar)
        {
            Socket sck = (Socket)ar.AsyncState;
            try
            {
                sck.EndDisconnect(ar);

                // Raise the event.
                this.OnDisconnect(new EventArgs());
            }
            catch (SocketException se)
            {
                this.OnSocketError(new SocketErrorEventArgs(se.ErrorCode, se.Message));
            }
            catch (ObjectDisposedException)
            {

            }
        }

        private void WaitForData()
        {
            SocketPacket myData = new SocketPacket();
            myData.assocSocket = this._sck;

            try
            {
                this._sck.BeginReceive(myData.dataBuffer, 0, myData.dataBuffer.Length, SocketFlags.None, new AsyncCallback(onDataReceived), myData);
            }
            catch (SocketException se)
            {
                this.OnSocketError(new SocketErrorEventArgs(se.ErrorCode, se.Message));
            }
        }

        //
        // Event raising methods
        //
        protected virtual void OnConnect(SocketConnectEventArgs e)
        {
            if (onConnected != null)
            {
                onConnected(this, e);
            }
        }

        protected virtual void OnConnectionAttempt(SocketConnectEventArgs e)
        {
            if (onConnecting != null)
            {
                onConnecting(this, e);
            }
        }

        protected virtual void OnDisconnect(EventArgs e)
        {
            this._sck.Close();
            this._sck = null;

            if (onDisconnected != null)
            {
                onDisconnected(this, e);
            }
        }

        protected virtual void OnDisconnectionAttempt(EventArgs e)
        {
            if (onDisconnecting != null)
            {
                onDisconnecting(this, e);
            }
        }

        protected virtual void OnRecieveData(SocketRecvEventArgs e)
        {
            if (onRecv != null)
            {
                onRecv(this, e);
            }
        }

        protected virtual void OnSendData(SocketSendEventArgs e)
        {
            if (onSend != null)
            {
                onSend(this, e);
            }
        }

        protected virtual void OnSocketError(SocketErrorEventArgs e)
        {
            this._sck.Close();
            this._sck = null;

            if (onError != null)
            {
                onError(this, e);
            }
        }
    }
}
